
use anyhow::Result;

use hyper::{Request, Client, body};
use hyper::client::HttpConnector;
use serde_json::Value;

use serde::{Serialize, Deserialize};
use serde::de::DeserializeOwned;

#[derive(Debug, Serialize, Deserialize)]
pub struct JsonRpcRequest {
    pub id: u32,
    pub jsonrpc: String,
    pub method: String,
    pub params: Vec<Value>,
}

impl JsonRpcRequest {
    pub fn with_params(method: &str, params: Vec<Value>) -> JsonRpcRequest {
        JsonRpcRequest {
            id: 1,
            jsonrpc: "2.0".to_owned(),
            method: method.to_owned(),
            params
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct JsonRpcError {
    pub code: u32,
    pub message: String,
    pub data: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct JsonRpcResponse<T> {
    pub id: u32,
    pub jsonrpc: String,
    pub result: Option<T>,
    pub error: Option<JsonRpcError>,
}

pub async fn do_request<T>(client: &Client<HttpConnector>, url: &str, token: &str, req: &JsonRpcRequest) -> Result<JsonRpcResponse<T>> where T: DeserializeOwned {
    let json = serde_json::to_string(req)?;

    let auth = format!("Bearer {}", token);
    let body = body::Body::from(json);

    let request = Request::builder()
        .uri(url)
        .header("Authorization", auth)
        .body(body)?;

    let rsp = client.request(request).await?;
    if rsp.status().is_success() {
        let buf = body::to_bytes(rsp).await?;
        let result: JsonRpcResponse<T> = serde_json::from_slice(&buf)?;
        Ok(result)
    } else {
        Err(anyhow::Error::msg(format!("http request got error response {}", rsp.status())))
    }
}

pub async fn jsonrpc_request<T>(client: &Client<HttpConnector>, url: &str, token: &str, req: JsonRpcRequest) -> Result<Option<T>> where T: DeserializeOwned {
    let rsp = do_request( client, url, token, &req).await?;
    if let Some(e) = rsp.error {
        return Err(anyhow::anyhow!("receive error from {} when call {}, {:?}", url, req.method, e))
    }
    Ok(rsp.result)
}